// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "types.h"

#include <algorithm>

using std::string;
using std::vector;

const std::map<string, string> rust_overrides = {
    {"any[]IN", "*const u8"}, {"any[]OUT", "*mut u8"}, {"any[]INOUT", "*mut u8"}};

const std::map<string, string> rust_primitives = {
    {"int8_t", "i8"},    {"int16_t", "i16"},     {"int32_t", "i32"},  {"int64_t", "i64"},
    {"uint8_t", "u8"},   {"uint16_t", "u16"},    {"uint32_t", "u32"}, {"uint64_t", "u64"},
    {"size_t", "usize"}, {"uintptr_t", "usize"}, {"int", "isize"},    {"char", "u8"},
    {"float", "f32"},    {"double", "f64"},
};

const std::map<string, string> rust_reserved_words = {
    {"proc", "proc_"},
};

bool has_attribute(const char* attr, const vector<string>& attrs) {
  return std::find(attrs.begin(), attrs.end(), attr) != attrs.end();
}

void dump_attributes(const vector<string>& attrs) {
  for (auto& a : attrs) {
    fprintf(stderr, "%s ", a.c_str());
  }
  fprintf(stderr, "\n");
}

string ArraySpec::kind_str() const {
  switch (kind) {
    case IN:
      return "IN";
    case OUT:
      return "OUT";
    default:
      return "INOUT";
  }
}

string ArraySpec::kind_lowercase_str() const {
  switch (kind) {
    case IN:
      return "in";
    case OUT:
      return "out";
    default:
      return "inout";
  }
}

bool ArraySpec::assign_kind(const vector<string>& attrs) {
  if (has_attribute("IN", attrs)) {
    kind = ArraySpec::IN;
  } else if (has_attribute("OUT", attrs)) {
    kind = ArraySpec::OUT;
  } else if (has_attribute("INOUT", attrs)) {
    kind = ArraySpec::INOUT;
  } else {
    return false;
  }
  return true;
}

string ArraySpec::to_string() const { return "[]" + kind_str(); }

const string map_override(const string& name, const std::map<string, string>& overrides) {
  auto ft = overrides.find(name);
  return (ft == overrides.end()) ? name : ft->second;
}

string TypeSpec::to_string() const { return type + (arr_spec ? arr_spec->to_string() : string()); }

string TypeSpec::as_cpp_declaration(bool is_wrapped) const {
  if (!arr_spec) {
    return type + " " + name;
  }

  if (is_wrapped && arr_spec->kind == ArraySpec::OUT && arr_spec->count == 1 &&
      type == "zx_handle_t") {
    return "user_out_handle* " + name;
  }

  string modifier = arr_spec->kind == ArraySpec::IN ? "const " : "";
  string ptr_type = type == "any" ? "void" : type;

  if (is_wrapped) {
    // This policy strings here matches the enum defined in the
    // kernel's user_ptr.h.
    string policy = arr_spec->kind_lowercase_str();
    return "user_" + policy + "_ptr<" + modifier + ptr_type + "> " + name;
  }
  return modifier + ptr_type + "* " + name;
}

string TypeSpec::as_rust_declaration() const {
  auto overridden = map_override(to_string(), rust_overrides);
  auto scalar_type = map_override(type, rust_primitives);
  auto safe_name = map_override(name, rust_reserved_words);

  if (overridden != to_string()) {
    return safe_name + ": " + overridden;
  } else if (!arr_spec) {
    return safe_name + ": " + scalar_type;
  } else {
    string ret = safe_name + ": ";
    if (arr_spec->kind == ArraySpec::IN) {
      ret += "*const ";
    } else {
      ret += "*mut ";
    }

    ret += scalar_type;
    if (arr_spec->count > 1)
      ret += " " + std::to_string(arr_spec->count);
    return ret;
  }
}

string TypeSpec::as_cpp_cast(const string& arg) const {
  if (!arr_spec) {
    return "static_cast<" + type + ">(" + arg + ")";
  }

  string modifier = arr_spec->kind == ArraySpec::IN ? "const " : "";
  string cast_type = type == "any" ? "void*" : type + "*";
  return "reinterpret_cast<" + modifier + cast_type + ">(" + arg + ")";
}

bool Syscall::is_vdso() const { return has_attribute("vdsocall", attributes); }

bool Syscall::is_noreturn() const { return has_attribute("noreturn", attributes); }

bool Syscall::is_blocking() const { return has_attribute("blocking", attributes); }

bool Syscall::is_internal() const { return has_attribute("internal", attributes); }

size_t Syscall::num_kernel_args() const {
  if (is_noreturn()) {
    return arg_spec.size();
  }

  // The first return value is passed as the ordinary C return
  // value, but only if there is at least one return value.
  size_t ret_values = ret_spec.size();
  if (ret_values > 0) {
    --ret_values;
  }
  return arg_spec.size() + ret_values;
}

void Syscall::for_each_return(const std::function<void(const TypeSpec&)>& cb) const {
  if (ret_spec.size() > 1) {
    std::for_each(ret_spec.begin() + 1, ret_spec.end(), cb);
  }
}

void Syscall::for_each_kernel_arg(const std::function<void(const TypeSpec&)>& cb) const {
  std::for_each(arg_spec.begin(), arg_spec.end(), cb);
  for_each_return(cb);
}

bool Syscall::validate() const {
  if (ret_spec.size() > 0 && is_noreturn()) {
    print_error("noreturn should have zero return arguments");
    return false;
  }

  if (num_kernel_args() > kMaxArgs) {
    print_error("invalid number of arguments");
    return false;
  }

  if (ret_spec.size() >= 1 && !ret_spec[0].name.empty()) {
    print_error("the first return argument cannot be named, yet...");
    return false;
  }

  if (is_blocking() && (ret_spec.size() == 0 || ret_spec[0].type != "zx_status_t")) {
    print_error("blocking must have first return be of type zx_status_t");
    return false;
  }

  if (is_vdso() && is_internal()) {
    print_error("vdsocall cannot be internal");
    return false;
  }

  bool valid_args = true;
  for_each_kernel_arg([this, &valid_args](const TypeSpec& arg) {
    if (arg.name.empty()) {
      print_error("all arguments need to be named, except the first return");
      valid_args = false;
    }
    if (arg.arr_spec) {
      if (!validate_array_spec(arg)) {
        valid_args = false;
      }
    }
  });
  return valid_args;
}

void Syscall::assign_index(int* next_index) {
  if (!is_vdso())
    index = (*next_index)++;
}

bool Syscall::validate_array_spec(const TypeSpec& ts) const {
  if (ts.arr_spec->count > 0)
    return true;
  // find arguments that represent the array count.
  for (const string& multiplier : ts.arr_spec->multipliers) {
    auto arg = std::find_if(arg_spec.begin(), arg_spec.end(),
                            [&](const TypeSpec& a) { return a.name == multiplier; });
    if (arg == arg_spec.end()) {
      std::string err = "invalid array spec for " + ts.name + ": '" + multiplier +
                        "' does not refer to an argument";
      print_error(err.c_str());
      return false;
    }
    // TODO:cpu also enforce INOUT here.
    if (arg->arr_spec && arg->arr_spec->count != 1u) {
      std::string err = "invalid array spec for " + ts.name + ": '" + multiplier +
                        "' refers to an array of size != 1";
      return false;
    }
  }
  return true;
}

void Syscall::print_error(const char* what) const {
  fprintf(stderr, "error: %s  : %s\n", name.c_str(), what);
}

string Syscall::return_type() const {
  if (ret_spec.empty()) {
    return "void";
  }
  return ret_spec[0].to_string();
}

bool Syscall::is_void_return() const { return return_type() == "void"; }
